package dbimpl

import (
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
	"time"

	"github.com/gomodule/redigo/redis"
	log "github.com/sirupsen/logrus"
)

// Rediskey
var (
	ErrKeyNotExist  = errors.New("key not exist")
	USER_EXPIRATION = 604800
	DAY_EXPIRATION  = 86400

	MONTH_EXPIRATION = 30 * 86400
)

// RedisImpl ...
type RedisImpl struct {
	client         *redis.Pool
	user_exptime   int
	mentor_exptime int
	prefix         string
}

// NewRedis ...
func NewRedis(siteid string, rcon string) (*RedisImpl, error) {
	spstr := strings.Split(rcon, ",")
	slen := len(spstr)
	if slen < 2 {
		return nil, nil
	}
	constr := spstr[0]
	passwd := spstr[1]
	db := 0
	if slen > 2 {
		sdb, err := strconv.Atoi(spstr[2])
		if err == nil {
			db = sdb
		}
	}
	log.WithField("Func:", "NewRedis").Infof("连接:%v,pwd:%v,db:%v", constr, passwd, db)
	redisc := redis.Pool{
		MaxIdle:     100,
		MaxActive:   2000,
		IdleTimeout: 180 * time.Second,
		Wait:        true,
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", constr, redis.DialPassword(passwd), redis.DialDatabase(db), redis.DialConnectTimeout(2*time.Second))
			if nil != err {
				return nil, err
			}
			return c, nil
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if time.Since(t) < time.Minute {
				return nil
			}
			_, err := c.Do("PING")
			return err
		},
	}
	c := redisc.Get()
	if c.Err() != nil {
		log.Warnf("redis Get : %v", c.Err())
		return nil, c.Err()
	}
	defer c.Close()
	RedisConn := &RedisImpl{
		client:         &redisc,
		user_exptime:   USER_EXPIRATION,
		mentor_exptime: DAY_EXPIRATION * 7,
		prefix:         "",
	}
	log.WithField("func", "NewRedis").Infof(" site:%v Exptime:%v Keeptime:%v", RedisConn.prefix, RedisConn.user_exptime, RedisConn.mentor_exptime)
	return RedisConn, c.Err()
}

// Reconnect ...
func (red *RedisImpl) Reconnect(err *error) bool {
	if *err == nil {
		return false
	}
	terr := *err
	maxretry := 3
	needNewConn := false
	for index := 0; index < maxretry; index++ {
		needNewConn = false
		if terr == nil {
			return false
		}
		if terr == io.EOF {
			needNewConn = true
		}
		if strings.Contains(terr.Error(), "use of closed network connection") {
			needNewConn = true
		}
		if strings.Contains(terr.Error(), "connect: connection refused") {
			needNewConn = true
		}
		if needNewConn == true {
			_, terr = red.client.Dial()
			log.Warnf("redis Reconnect...%v", needNewConn)
		} else {
			break
		}
		if terr != nil {
			continue
		}
	}
	return needNewConn
}

// Close ...
func (red *RedisImpl) Close() error {
	return red.client.Close()
}

// Puslish ...
func (red *RedisImpl) Puslish(data []byte) error {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis Get error: %v", rd.Err())
		return rd.Err()
	}
	defer rd.Close()
	key := red.prefix + "ClearUserSendFlag"
	_, err := rd.Do("Publish", key, data)
	if err != nil {
		log.Warnf("Publish %v msg: %v err [%v]", key, string(data), err)
	}
	return err
}

func (red *RedisImpl) Incry(key string, v int64) (interface{}, error) {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("CacheKey redis Get error: %v", rd.Err())
		return nil, rd.Err()
	}
	prekey := red.prefix + key
	defer rd.Close()
	value, err := rd.Do("INCRBY", prekey, v)
	return value, err
}

// UpdateKey ...
func (red *RedisImpl) UpdateKey(key string, v interface{}, exptime int) error {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("CacheKey redis Get error: %v", rd.Err())
		return rd.Err()
	}
	prekey := red.prefix + key
	defer rd.Close()

	_, err := rd.Do("SET", prekey, v, "EX", exptime)
	return err
}

// UpdateMap ...
func (red *RedisImpl) UpdateMap(key string, field string, value int64, expiretime int) (interface{}, error) {
	var reerr error
	defer red.Reconnect(&reerr)

	map_key := fmt.Sprintf("%s%s", red.prefix, key)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("CacheKey redis Get error: %v", rd.Err())
		return nil, rd.Err()
	}

	defer rd.Close()
	rd.Send("HINCRBY", map_key, field, value)
	if expiretime > 0 {
		rd.Send("EXPIRE", map_key, expiretime)
	}
	rd.Flush()
	reply, err := rd.Receive()
	if err != nil {
		log.Warnf("UpdateMap error: %v, key:%s, field:%s, value:%d", err, map_key, field, value)
	}
	return reply, err
}

// GetKey ...
func (red *RedisImpl) GetKey(key string) ([]byte, error) {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis Get UID %v error: %v", key, rd.Err())
		return nil, rd.Err()
	}
	defer rd.Close()
	prekey := red.prefix + key
	retval, err := redis.Bytes(rd.Do("GET", prekey))
	if err != nil {
		reerr = err
		return nil, ErrKeyNotExist
	}
	return retval, nil
}

// GetKey ...
func (red *RedisImpl) GetKeyInterface(key string) (interface{}, error) {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis Get UID %v error: %v", key, rd.Err())
		return nil, rd.Err()
	}
	defer rd.Close()
	prekey := red.prefix + key
	retval, err := rd.Do("GET", prekey)
	if err != nil || retval == nil {
		reerr = err
		return nil, ErrKeyNotExist
	}
	return retval, nil
}

// GetKey ...
func (red *RedisImpl) GetKeyTTL(key string) (int, error) {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis Get UID %v error: %v", key, rd.Err())
		return 0, rd.Err()
	}
	defer rd.Close()
	prekey := red.prefix + key
	retval, err := redis.Int(rd.Do("TTL", prekey))
	if err != nil {
		reerr = err
		return 0, ErrKeyNotExist
	}
	return retval, nil
}

// set string ...
func (red *RedisImpl) Set(key, value string) error {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis set UID %v error: %v", key, rd.Err())
		return reerr
	}
	defer rd.Close()
	prekey := red.prefix + key
	_, err := rd.Do("Set", prekey, value)
	if err != nil {
		reerr = err
		return reerr
	}
	return nil
}

// GetKey ...
func (red *RedisImpl) GetKeyString(key string) (string, error) {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis Get UID %v error: %v", key, rd.Err())
		return "", rd.Err()
	}
	defer rd.Close()
	prekey := red.prefix + key
	retval, err := redis.String(rd.Do("GET", prekey))
	if err != nil {
		reerr = err
		return "", ErrKeyNotExist
	}
	return retval, nil
}

// DelKey ...
func (red *RedisImpl) DelKey(key string) error {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis Get UID %v error: %v", key, rd.Err())
		return rd.Err()
	}
	defer rd.Close()
	prekey := red.prefix + key
	_, err := rd.Do("DEL", prekey)
	if err != nil {
		reerr = err
		return reerr
	}
	return nil
}
func (red *RedisImpl) HashHMset(key string, fileds ...string) error {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis Get UID %v error: %v", key, rd.Err())
		return rd.Err()
	}
	defer rd.Close()
	prekey := red.prefix + key
	hvals := make([]interface{}, 0)
	hvals = append(hvals, prekey)
	for _, v := range fileds {
		hvals = append(hvals, v)
	}
	_, err := rd.Do("HMSET", hvals...)
	if err != nil {
		reerr = err
		return reerr
	}
	return nil
}
func (red *RedisImpl) HashHDEL(key string, fileds ...string) error {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis Get UID %v error: %v", key, rd.Err())
		return rd.Err()
	}
	defer rd.Close()

	prekey := red.prefix + key

	hvals := make([]interface{}, 0)
	hvals = append(hvals, prekey)
	for _, v := range fileds {
		hvals = append(hvals, v)
	}
	_, err := rd.Do("HDEL", hvals...)
	if err != nil {
		reerr = err
		return reerr
	}
	return nil
}

// HashHMget ...
func (red *RedisImpl) HashHMget(key string, fileds ...string) ([]interface{}, error) {
	var reerr error
	defer red.Reconnect(&reerr)

	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis Get UID %v error: %v", key, rd.Err())
		return nil, rd.Err()
	}
	defer rd.Close()

	prekey := red.prefix + key

	hvals := make([]interface{}, 0)
	hvals = append(hvals, prekey)
	for _, v := range fileds {
		hvals = append(hvals, v)
	}
	value, err := redis.Values(rd.Do("HMGET", hvals...))
	if err != nil {
		reerr = err
		return nil, reerr
	}
	return value, nil
}
func (red *RedisImpl) HGetAll(key string) (error, map[string]int64) {
	var reerr error
	defer red.Reconnect(&reerr)

	rd := red.client.Get()
	if rd.Err() != nil {
		return nil, nil
	}
	defer rd.Close()
	prekey := red.prefix + key
	reply, err := redis.Int64Map(rd.Do("HGETALL", prekey))
	if err != nil {
		log.WithField("func:", "Getlastfeedtime").Error(err)
		return nil, nil
	}
	return nil, reply
}

// Mget ...
func (red *RedisImpl) Mget(keys ...string) ([]string, error) {
	var reerr error
	defer red.Reconnect(&reerr)

	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		//log.Warnf("redis Get UID %v error: %v", key, rd.Err())
		return nil, rd.Err()
	}
	defer rd.Close()

	hvals := make([]interface{}, 0)
	for _, key := range keys {
		hvals = append(hvals, red.prefix+key)
	}
	value, err := redis.Strings(rd.Do("MGET", hvals...))
	if err != nil {
		reerr = err
		return nil, reerr
	}
	return value, nil
}

// set expire ...
func (red *RedisImpl) SetExpire(key string, expire int) error {
	var reerr error
	defer red.Reconnect(&reerr)
	rd := red.client.Get()
	if rd.Err() != nil {
		reerr = rd.Err()
		log.Warnf("redis set UID %v error: %v", key, rd.Err())
		return reerr
	}
	defer rd.Close()
	_, err := rd.Do("expire", key, expire)
	if err != nil {
		reerr = err
		return reerr
	}
	return nil
}
